﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using AutoMapper;
using good1.ApplicationServices.Interface;
using good1.Core;
using good1.Core.Enums;
using SharpArch.Core;
using SharpArch.Core.PersistenceSupport;

namespace good1.ApplicationServices
{
    public class AuthenticationService : IAuthenticationService
    {
        //TODO when we come to doing more than basic let's look at this http://ayende.com/Blog/archive/2007/11/17/A-vision-of-enterprise-platform-Security-Infrastructure.aspx

        public AuthenticationService(IRepository<EmailAddress> emailAddressRepository, IRepository<User> userRepository, ICryptographerService cryptopherService, IRepository<Donor> donorRepository)
        {
            Check.Require(emailAddressRepository != null, "emailAddressRepository may not be null");
            Check.Require(userRepository != null, "userRepository may not be null");
            Check.Require(cryptopherService != null, "cryptopherService may not be null");
            Check.Require(donorRepository != null, "donorRepository may not be null");
            this.donorRepository = donorRepository;
            this.emailAddressRepository = emailAddressRepository;
            this.userRepository = userRepository;
            this.cryptopherService = cryptopherService;         
        }

        public bool ValidateUser(string userName, string password)
        {
            if (string.IsNullOrEmpty(userName)) return false;
            if (string.IsNullOrEmpty(password)) return false;

            User user = GetByUserName(userName);
            if (user == null) return false;

            return cryptopherService.GetPasswordHash(password, user.PasswordSalt) == user.PasswordHash;      
        }

        public User GetByProviderUserKey(Guid providerUserKey)
        {
            IDictionary<string, object> propertyValues = new Dictionary<string, object>();
            propertyValues.Add("ProviderUserKey", providerUserKey);
            User user = userRepository.FindOne(propertyValues);
            return user;
        }
        
        public User GetByUserName(string userName)
        {
            //NOTE probably better to think of using queries than FindOne, but easily replaced here if trouble.
            //though it's nice mocking out easily on the FindOne, so it would be necessary to extend IRepository somehow.
            //http://groups.google.com/group/sharp-architecture/browse_thread/thread/5550326cb6d39e1a/745dbb5be3e4631d?lnk=gst&q=extending#745dbb5be3e4631d 
            IDictionary<string, object> propertyValues = new Dictionary<string, object>();
            propertyValues.Add("Username", userName);
            User user = userRepository.FindOne(propertyValues);
            return user;
        }

        public GoodRoles[] GetRolesByUserName(string userName)
        {
            User user = GetByUserName(userName);
            return GetRolesFromBits(user.RolesBitwise);
        }

        public string GetRolesByUserNameInPipeFormat(string userName)
        {
            StringBuilder rolesInPipe = new StringBuilder(100);
            GoodRoles[] roles = GetRolesByUserName(userName);
            int count = 1;
            foreach (GoodRoles goodRole in roles)
            {
                rolesInPipe.Append(goodRole);
                if (count != roles.Count()) {
                    rolesInPipe.Append("|"); }
                count++;
            }
            return rolesInPipe.ToString();
        }

        public int HowLongToRemember(bool rememberAtAll)
        {
            int howLongToRemember = 15;
            return rememberAtAll ? howLongToRemember : 0;
        }

        public GoodRoles[] GetRolesFromBits(int userRoles)
        {
            IList<GoodRoles> roles = new List<GoodRoles>();
            foreach (GoodRoles goodRole in Enum.GetValues(typeof(GoodRoles)))
            {
                if (((GoodRoles)userRoles & goodRole) != 0)
                {
                    roles.Add(goodRole);
                }
            }
            return roles.ToArray();
        }

       
        public User Register(User user, bool wantsMonthlyUpdates, string password, string reminderQuestion, string reminderAnswer)
        {

            EmailAddress donorEmail = new EmailAddress { Email = user.EmailAddress };
            emailAddressRepository.SaveOrUpdate(donorEmail);

            //TODO consider writing mapper for these types to auotmapper.
            //i.e. userForm would have password but map it into User
            user.PasswordSalt = cryptopherService.CreateSalt();
            user.PasswordHash = cryptopherService.GetPasswordHash(password, user.PasswordSalt);
            user.IsApproved = false;
            

            if(string.IsNullOrEmpty(user.Username))
            {
                user.Username = user.EmailAddress;
            }

            if (user.Birthday < DateTime.Now.AddYears(-200))
            {
                user.Birthday = null;
            }

            //we register them as a donor, which makes them a user as well.
            Mapper.CreateMap<User, Donor>();
            Donor donor = Mapper.Map<User, Donor>(user);
            donor.WantsMonthlyUpdates = wantsMonthlyUpdates;
            donor = donorRepository.SaveOrUpdate(donor);

            //service simply wants back a user for now.
            user = userRepository.Get(donor.Id);

            return user;

        }

        public bool CheckAvailability(string emailAddress)
        {
            IQueryable<User> users = userRepository.GetAll().AsQueryable().Where(u => u.EmailAddress.Equals(emailAddress));

            if (users.Count() > 0)
            {
                return false;
            }

            return true;
        }

        public User ApproveEmailAddress(Guid key)
        {
            User user = GetByProviderUserKey(key);

            if (user != null && user.IsApproved == false)
            {
                user.IsApproved = true;
                userRepository.SaveOrUpdate(user);
            }
            else
            {
                // Wrong GUID or user was already approved
            }

            return user;
        }


        private readonly ICryptographerService cryptopherService;
        private readonly IRepository<EmailAddress> emailAddressRepository;
        private readonly IRepository<User> userRepository;
        private readonly IRepository<Donor> donorRepository;
    }
}
